package member

import (
	"math/rand"
	"phant-operator/pkg/apis/label"
	"phant-operator/pkg/apis/phant/v1alpha1"
	"phant-operator/pkg/controller"
	"phant-operator/pkg/manager"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/klog/v2"
)

var (
	baseChars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ"
	numbers   = "0123456789"
	xchars    = "!@#$%^&*()-."

	xlength = 16
)

type postgresMemberManager struct {
	deps *controller.Dependencies
}

// NewPatroniManager returns a *patroniManager
func NewPostgresMemberManager(dependencies *controller.Dependencies) manager.Manager {
	return &postgresMemberManager{
		deps: dependencies,
	}
}

func (m *postgresMemberManager) Sync(pg *v1alpha1.PostgresCluster) error {
	// Sync Cluster Headless Service
	if _, err := m.syncPostgresConfigMapForPostgresCluster(pg); err != nil {
		return err
	}
	// sync endpoints named with instance name
	if err := m.syncPostgresEndPointForPostgresCluster(pg.GetInstanceName(), pg); err != nil {
		return err
	}

	// sync endpoints named with instance config name
	if err := m.syncPostgresServiceForPostgresCluster(pg.GetInstanceName()+"-config", pg); err != nil {
		return err
	}

	return nil
}

func (m *postgresMemberManager) syncPostgresServiceForPostgresCluster(svcName string, pg *v1alpha1.PostgresCluster) error {
	klog.V(5).Infof("Sync Services %s for postgres cluster %s/%s", svcName, pg.GetNamespace(), pg.Name)
	svc, err := m.deps.ServiceLister.Services(pg.GetNamespace()).Get(svcName)
	if errors.IsNotFound(err) {
		return nil
	}
	if err != nil {
		return err
	}
	svc.SetOwnerReferences([]metav1.OwnerReference{controller.GetOwnerRef(pg)})
	_, err = m.deps.Controls.ServiceControl.Update(pg, svc)
	if err != nil {
		return err
	}

	return nil
}

func (m *postgresMemberManager) syncPostgresEndPointForPostgresCluster(endpoints string, pg *v1alpha1.PostgresCluster) error {
	eps, err := m.deps.EndpointLister.Endpoints(pg.GetNamespace()).Get(endpoints)
	if errors.IsNotFound(err) {
		return nil
	}
	if err != nil {
		return err
	}
	eps.SetOwnerReferences([]metav1.OwnerReference{controller.GetOwnerRef(pg)})
	_, err = m.deps.Controls.EndpointsControl.Update(pg, eps)
	if err != nil {
		return err
	}

	return nil
}

func (m *postgresMemberManager) syncPostgresConfigMapForPostgresCluster(pg *v1alpha1.PostgresCluster) (*corev1.ConfigMap, error) {

	cmName := controller.PostgresMemberName(pg.Name)

	klog.V(5).Infof("Sync configMap %s for postgres cluster %s/%s", cmName, pg.GetNamespace(), pg.Name)
	cm := getPostgresConfigMap(pg)

	oldCm, err := m.deps.Controls.ConfigMapControl.Get(pg, cm)
	if errors.IsNotFound(err) {
		newCm := m.setPostgresConfigMapData(cm)
		klog.Infof("Create configMap %s for postgres cluster %s/%s", cmName, pg.GetNamespace(), pg.Name)
		return m.deps.Controls.ConfigMapControl.Create(pg, newCm)
	}
	if err != nil {
		klog.Errorf("Get configMap %s for postgres cluster %s/%s, err: %v", cmName, pg.GetNamespace(), pg.Name, err)
		return nil, err
	}
	return oldCm, nil

}

func getPostgresConfigMap(pg *v1alpha1.PostgresCluster) *corev1.ConfigMap {
	instanceName := pg.GetInstanceName()
	postgresLabel := label.New().Instance(instanceName).Labels()

	return &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:            controller.PostgresMemberName(pg.Name),
			Namespace:       pg.Namespace,
			Labels:          postgresLabel,
			OwnerReferences: []metav1.OwnerReference{controller.GetOwnerRef(pg)},
		},
	}
}

func (m *postgresMemberManager) setPostgresConfigMapData(cm *corev1.ConfigMap) *corev1.ConfigMap {
	var (
		primaryPassword string = generatePostgresPassword(xlength)
		standbyPassword string = generatePostgresPassword(xlength)
	)
	cm.Data = map[string]string{
		rolePrimary: primaryPassword,
		roleStandby: standbyPassword,
	}
	return cm
}

func generatePostgresPassword(length int) string {
	source := numbers + baseChars + xchars
	result := make([]byte, length, length)
	for i := 0; i < length; i++ {
		index := rand.Intn(len(source))
		result[i] = source[index]
	}
	return string(result)

}
